#include "PhysxLogic.h"
#include <iostream>

#include <NxForceField.h>
#include <NxForceFieldLinearKernel.h>
#include <NxForceFieldShape.h>

#include <NxForceFieldDesc.h>
#include <NxForceFieldLinearKernelDesc.h>
#include <NxForceFieldShapeDesc.h>
#include <NxSphereForceFieldShape.h>

#include <NxActorDesc.h>
#include <NxSphereForceFieldShapeDesc.h>
#include <NxBoxForceFieldShapeDesc.h>
#include <NxPlaneShapeDesc.h>
#include <NxBoxShapeDesc.h>
#include <NxSphereShapeDesc.h>

#include "..\_Utils\src\PhysxConsoleOutputStream.h"

//variables
NxPhysicsSDK *PhysxLogic::physicsSDK = NULL;
NxScene *PhysxLogic::physicsScene = NULL;

Scene *PhysxLogic::sampleScene = NULL;
Camera *PhysxLogic::cam = NULL;

DebugRenderer *PhysxLogic::debugRenderer = NULL;
const NxDebugRenderable *PhysxLogic::debugRenderable = NULL;
bool PhysxLogic::debugRenderingIsEnabled = false;

bool PhysxLogic::Init(Scene *scene)
{
    sampleScene = scene;
    NxPhysicsSDKDesc sdkDesc;
    physicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, NULL, &physxConsoleOutputStream, sdkDesc);
    if(physicsSDK == NULL) 
    {
        return false;
    }

    NxSceneDesc sceneDesc;
    
    physicsScene = physicsSDK->createScene(sceneDesc);
    if(physicsScene == NULL) 
    {
        return false;
    }

    debugRenderer = new DebugRenderer();
    debugRenderer->InitDebugRenderer(physicsSDK);

    return true;
}

void PhysxLogic::PrepareScene()
{
    sampleScene->PrepareScene(physicsScene);
}

void PhysxLogic::SimulateAndRender()
{
    if(physicsScene == NULL) {
        return;
    }

    const int newTime = glutGet(GLUT_ELAPSED_TIME);
    const int deltaTime = newTime - Renderer::elapsedTime;

    Renderer::elapsedTime = newTime;

    sampleScene->Update();

    NxReal maxTimeStep; NxU32 maxIter; NxTimeStepMethod method;
    physicsScene->getTiming(maxTimeStep, maxIter, method, NULL);
    float timeToSimulate = (maxTimeStep*maxIter*60*(deltaTime/1000.0f));
    physicsScene->simulate(timeToSimulate);
    physicsScene->flushStream();

    Draw();
    
    physicsScene->fetchResults(NX_RIGID_BODY_FINISHED, true);

    debugRenderable = physicsScene->getDebugRenderable();
}

void PhysxLogic::Exit()
{
    if(sampleScene != NULL)
        delete sampleScene;
    if(physicsSDK != NULL)
    {
        if(physicsScene != NULL)
            physicsSDK->releaseScene(*physicsScene);
        NxReleasePhysicsSDK(physicsSDK);
        physicsScene = NULL;
        physicsSDK = NULL;
    }
}

NxActor *PhysxLogic::CreateBox( const NxVec3 &pos, const NxVec3 &dim, const NxVec3 &orientation, bool isStatic, float density)
{
    if(physicsScene == NULL) {
        return NULL;
    }

    NxBodyDesc bodyDesc;
    bodyDesc.angularDamping	= 0.99f;
    bodyDesc.linearDamping	= 0.99f;

    NxBoxShapeDesc boxDesc;
    boxDesc.dimensions = dim;

    NxActorDesc actorDesc;
    actorDesc.shapes.pushBack(&boxDesc);
    actorDesc.body			= (isStatic == true)? NULL : &bodyDesc;
    actorDesc.density		= density;
    actorDesc.globalPose.t  = pos;
    actorDesc.globalPose.M  = CalcOrientationMatrix(orientation);

    return physicsScene->createActor(actorDesc);
}

NxActor *PhysxLogic::CreateSphere(const NxVec3 &pos, float radius, bool isStatic, float density)
{
    if(physicsScene == NULL) {
        return NULL;
    }

    NxBodyDesc bodySphereDesc;
    bodySphereDesc.angularDamping	= 0.99f;
    bodySphereDesc.linearDamping	= 0.99f;

    NxSphereShapeDesc sphereDesc;
    sphereDesc.radius = radius;
    
    NxActorDesc actorSphereDesc;	
    actorSphereDesc.shapes.pushBack(&sphereDesc);	
    actorSphereDesc.body = (isStatic) ? NULL : &bodySphereDesc;
    actorSphereDesc.density = density;
    actorSphereDesc.globalPose.t = pos;

    return physicsScene->createActor(actorSphereDesc);
}

NxMat33 PhysxLogic::CalcOrientationMatrix(const NxVec3 &angles)
{
    NxQuat q1, q2, q3;
    q1.fromAngleAxis(angles.x, NxVec3(1,0,0));
    q2.fromAngleAxis(angles.y, NxVec3(0,1,0));
    q3.fromAngleAxis(angles.z, NxVec3(0,0,1));

    NxMat33 orient;
    orient.fromQuat(q1 * q2 * q3);
    return orient;
}

void PhysxLogic::ThrowBall(float size, float power)
{
    NxVec3 p = cam->GetPosition();
    NxVec3 vel = cam->GetDirection();

    vel.normalize();
    vel*=power;	

    NxBodyDesc bodyDesc;
    bodyDesc.angularDamping	= 0.5f;
    bodyDesc.linearVelocity = vel;

    NxSphereShapeDesc sphereDesc;
    sphereDesc.radius = size;

    NxActorDesc actorDesc;
    actorDesc.shapes.pushBack(&sphereDesc);
    actorDesc.body			= &bodyDesc;
    actorDesc.density		= 10.0f;
    actorDesc.globalPose.t  = p;

    physicsScene->createActor(actorDesc);
}

void PhysxLogic::HandleKeyboardEvent(unsigned char key, int x, int y)
{
    sampleScene->HandleKeyboardEvent(key, x, y);
    switch(key)
    {
        case ' ':
        {
            double size = NxMath::rand(0.2f, 1.8f);
            ThrowBall(size, 10.0f);
            break;
        }
        case 'q': //reset scene
        {
            sampleScene->CleanScene();
            int nbActors = physicsScene->getNbActors();
            NxActor** actors = physicsScene->getActors();
            while(nbActors--) {
                NxActor* actor = *actors++;
                physicsScene->releaseActor(*actor);
            }
            int nbForceFields = physicsScene->getNbForceFields();
            NxForceField** forceFields = physicsScene->getForceFields();
            while(nbForceFields--) {
                NxForceField* ff = *forceFields++;
                physicsScene->releaseForceField(*ff);
            }
            sampleScene->PrepareScene(physicsScene);
            break;
        }
    }
}

void PhysxLogic::HandleKeyboardSpecialEvent(int key, int x, int y)
{
    sampleScene->HandleKeyboardSpecialEvent(key, x, y);
}

void PhysxLogic::HandleMouseClickEvent(int button, int state, int x, int y)
{
    sampleScene->HandleMouseClickEvent(button, state, x, y);
}

void PhysxLogic::HandleMousePressedMovementEvent(int x, int y, int dx, int dy)
{
    cam->Rotate(16 * dx, 16 * dy);
    sampleScene->HandleMousePressedMovementEvent(x, y, dx, dy);
}

void PhysxLogic::Draw()
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    if(cam != NULL)
        cam->Update();

    Renderer::ResetLightPosition();

    //draw things

    if(physicsScene == NULL)
        return;

    float matrix[16];
    int nbActors = physicsScene->getNbActors();
    NxActor **actors = physicsScene->getActors();
    while(nbActors--)
    {
        NxActor *actor = *actors++;

        if(actor->userData != NULL)
        {
            //IGNORE THIS ACTORS, THEY WILL DRAW THEMSELVES
            continue; 
        }

        int nbShapes = actor->getNbShapes();
        NxShape *const *shapes = actor->getShapes();
        while(nbShapes--)
        {
            NxShape *shape = shapes[nbShapes];
            switch(shape->getType())
            {
                case NX_SHAPE_BOX:
                {
                    NxBoxShape *box = shape->isBox();
                    NxVec3 boxDimension = box->getDimensions();
                    box->getGlobalPose().getColumnMajor44(matrix);
                    glPushMatrix();
                    glMultMatrixf(matrix);
                    glColor3f(1.0f, 0.7f, 0.5f);
                    Renderer::DrawBox(boxDimension);
                    //Border
                    /*glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
                    glLineWidth(2.5f);
                    glColor3f(0.0f, 0.0f, 0.0f);
                    Renderer::DrawBox(boxDimension);
                    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);*/
                    //
                    glPopMatrix();
                    break;
                }
                case NX_SHAPE_SPHERE:
                {
                    NxSphereShape *sphere = shape->isSphere();
                    float radius = sphere->getRadius();
                    sphere->getGlobalPose().getColumnMajor44(matrix);
                    glPushMatrix();
                    glMultMatrixf(matrix);
                    glColor3f(0.7f, 0.8f, 0.5f);
                    glutSolidSphere(radius, 16, 16);
                    glPopMatrix();
                    break;
                }
            }
        }
    }

    sampleScene->Draw();

    if(debugRenderable != NULL)
        debugRenderer->RenderDebugData((NxDebugRenderable *)debugRenderable);

    glutSwapBuffers();
}